#!/usr/bin/python
# Resource monitor - client-side daemon
#
# Uses psutil to monitor system resources, and updates the data over USB serial port
# to the hardware monitor.
#
########################################################

import psutil	#https://github.com/giampaolo/psutil
import pynvml	#https://pypi.python.org/pypi/nvidia-ml-py/
import sensors	#https://pypi.python.org/pypi/PySensors/

import serial
import os
import sys
import time

buf = list("CXXXXMX RXWXUXDXRXHXDXBX  dd\xDFdd\xDF")
ser = 0

def __escape_byte(message, b):
	if (b == 0x7e or b == 0x7d):
		message.append(0x7d);
		message.append(b ^ 0x20);
	else:
		message.append(b);

def fsp_write(command, data):
	message = [0x7e]
	__escape_byte(message, len(data) + 1)
	__escape_byte(message, command)
	checksum = command
	for b in data:
		b = ord(b)
		__escape_byte(message, b)
		checksum = (checksum + b) & 0xFF
	__escape_byte(message, (0xFF - checksum) % 0xFF)
	return ''.join(chr(b) for b in message)		#This is a string of raw byte values which can be written to the serial port

def main():
	state = {
		"cpu": [], "memory": [],
		"gpu": [], "gpu_memory": [], "gpu_count": pynvml.nvmlDeviceGetCount(),
		"disk_read": [], "disk_write": [], "disk_read_last": psutil.disk_io_counters().read_bytes, "disk_write_last": psutil.disk_io_counters().write_bytes,
		"net_upload": [], "net_download": [], "net_upload_last": psutil.net_io_counters().bytes_sent, "net_download_last": psutil.net_io_counters().bytes_recv
	}

	temp_time = 0		#0 shows temperature, 1 shows time
	timer = 0

	while True:
		exception = False

		#CPU Usage:
		state["cpu"].append(int(psutil.cpu_percent() / 11));
		if (len(state["cpu"]) > 300):
			state["cpu"].pop(0)
		last10 = state["cpu"][-10:]
		last60 = state["cpu"][-60:]

		buf[1] = writeBarGraph(state["cpu"][-1])
		buf[2] = writeBarGraph(sum(last10) / len(last10))
		buf[3] = writeBarGraph(sum(last60) / len(last60))
		buf[4] = writeBarGraph(sum(state["cpu"]) / len(state["cpu"]))

		#Memory Usage:
		state["memory"].append(int(psutil.virtual_memory().percent / 11))
		if (len(state["memory"]) > 10):
			state["memory"].pop(0)
		buf[6] = writeBarGraph(state["memory"][-1])
		buf[7] = writeBarGraph(sum(state["memory"]) / len(state["memory"]))

		#Disk I/O:
		disk = psutil.disk_io_counters()

		state["disk_read"].append(disk.read_bytes - state["disk_read_last"])
		state["disk_read_last"] = disk.read_bytes
		state["disk_write"].append(disk.write_bytes - state["disk_write_last"])
		state["disk_write_last"] = disk.write_bytes

		if (len(state["disk_read"]) > 10):
			state["disk_read"].pop(0)
		if (len(state["disk_write"]) > 10):
			state["disk_write"].pop(0)

		buf[9] = writeBarGraph(scaleDisk(sum(state["disk_read"]) / len(state["disk_read"])))
		buf[11] = writeBarGraph(scaleDisk(sum(state["disk_write"]) / len(state["disk_write"])))


		#Network I/O:
		net = psutil.net_io_counters()

		state["net_download"].append(net.bytes_recv - state["net_download_last"])
		state["net_download_last"] = net.bytes_recv
		state["net_upload"].append(net.bytes_sent - state["net_upload_last"])
		state["net_upload_last"] = net.bytes_sent

		if (len(state["net_download"]) > 10):
			state["net_download"].pop(0)
		if (len(state["net_upload"]) > 10):
			state["net_upload"].pop(0)

		buf[13] = writeBarGraph(scaleNetwork(sum(state["net_upload"]) / len(state["net_upload"])))
		buf[15] = writeBarGraph(scaleNetwork(sum(state["net_download"]) / len(state["net_download"])))


		if temp_time == 0:
			#CPU Temperature:
			for chip in sensors.iter_detected_chips():
				for feature in chip:
					if (feature.label == "Physical id 0" or feature.label == "Package id 0"):
						cpu_temp = int(feature.get_value())
						if (cpu_temp > 99):
							cpu_temp = 99
						buf[26] = str(cpu_temp / 10)
						buf[27] = str(cpu_temp % 10)
						buf[28] = '\xDF'

			#GPU Temperature:
			gpuHandle = pynvml.nvmlDeviceGetHandleByIndex(0)	#If you have multiple GPUs, iterate over them here and average the results
			gpu_temp = pynvml.nvmlDeviceGetTemperature(gpuHandle, pynvml.NVML_TEMPERATURE_GPU)
			if (gpu_temp > 99):
				gpu_temp = 99
			buf[29] = str(gpu_temp / 10)
			buf[30] = str(gpu_temp % 10)
			buf[31] = '\xDF'
		else:
			time_string = time.strftime("%I:%M%p")
			if time_string[0] != '0':
				buf[26] = time_string[0]
			else:
				buf[26] = ' '
			buf[27] = time_string[1]
			buf[28] = time_string[2]
			buf[29] = time_string[3]
			buf[30] = time_string[4]
			buf[31] = time_string[5]

		#Rotate through mount points and show percentage
		buf[17] = writeBarGraph(int(psutil.disk_usage("/").percent / 12.5))
		buf[19] = writeBarGraph(int(psutil.disk_usage("/home").percent / 12.5))
		buf[21] = writeBarGraph(int(psutil.disk_usage("/mnt/data").percent / 12.5))
		buf[23] = writeBarGraph(int(psutil.disk_usage("/mnt/backup").percent / 12.5))

		timer = timer + 1
		if (timer >= 1):
			timer = 0;
			temp_time = temp_time + 1
			if (temp_time > 1):
				temp_time = 0;

		sendData()

		time.sleep(1)

def sendData():
	ser.write(fsp_write(0x01, buf))

def scaleNetwork(value):
	if (value >   0x1000000):
		return 9
	elif (value > 0x400000):
		return 8
	elif (value > 0x100000):
		return 7
	elif (value > 0x40000):
		return 6
	elif (value > 0x10000):
		return 5
	elif (value > 0x4000):
		return 4
	elif (value > 0x1000):
		return 3
	elif (value > 0x400):
		return 2
	elif (value > 0x100):
		return 1
	else:
		return 0

def scaleDisk(value):
	if (value >   0x10000000):
		return 9
	elif (value > 0x4000000):
		return 8
	elif (value > 0x1000000):
		return 7
	elif (value > 0x400000):
		return 6
	elif (value > 0x100000):
		return 5
	elif (value > 0x40000):
		return 4
	elif (value > 0x10000):
		return 3
	elif (value > 0x4000):
		return 2
	elif (value > 0x1000):
		return 1
	else:
		return 0

def writeBarGraph(value):
	if (value <= 0):
		return ' '
	elif (value >= 8):
		return '\xff'
	else:
		return chr(value)

########## Main startup hooks here ##########
if (__name__=="__main__"):
	pynvml.nvmlInit()
	sensors.init()

	try:
		while (True):
			try:
				ser = serial.Serial("/dev/ttyResourceMonitor", 19200)
				main()
			except serial.SerialException as e:
				print(e)
				time.sleep(1)
			finally:
				if ("close" in dir(ser)):
					ser.close()
					print("Closing serial port")
	except:
		raise
	finally:
		pynvml.nvmlShutdown()
		sensors.cleanup()
